#include "GLES2HardwareBufferManager.h"
#include "GLES2HardwareVertexBuffer.h"
#include "GLES2HardwareIndexBuffer.h"

namespace PVM
{
	struct GLES2ScratchBufferAlloc
	{
		uint32 size: 31;
		uint32 free: 1;
	};
	#define SCRATCH_POOL_SIZE 1 * 1024 * 1024
	#define SCRATCH_ALIGNMENT 32
	
	GLES2HardwareBufferManagerBase::GLES2HardwareBufferManagerBase()
	{
		mScratchBufferPool = static_cast<char*>(malloc(SCRATCH_POOL_SIZE));
		GLES2ScratchBufferAlloc* ptrAlloc = (GLES2ScratchBufferAlloc*)mScratchBufferPool;
		ptrAlloc->size = SCRATCH_POOL_SIZE - sizeof(GLES2ScratchBufferAlloc);
		ptrAlloc->free = 1;
		
		mMapBufferThreshold = 0;
	}
	
	GLES2HardwareBufferManagerBase::~GLES2HardwareBufferManagerBase()
	{
		destroyAllDeclarations();
		destroyAllBindings();
		
		free(mScratchBufferPool);
	}
	
	HardwareVertexBufferSharedPtr
		GLES2HardwareBufferManagerBase::createVertexBuffer(size_t vertexSize,
														   size_t numVerts,
														   HardwareBuffer::Usage usage,
														   bool useShadowBuffer)
	{
		GLES2HardwareVertexBuffer* buf =
		new GLES2HardwareVertexBuffer(this, vertexSize, numVerts, usage, true);
		{
			LOCK_MUTEX(mVertexBuffersMutex);
			mVertexBuffers.insert(buf);
		}
		return HardwareVertexBufferSharedPtr(buf);
	}
	
	HardwareIndexBufferSharedPtr
	GLES2HardwareBufferManagerBase::createIndexBuffer(HardwareIndexBuffer::IndexType itype,
													  size_t numIndexes,
													  HardwareBuffer::Usage usage,
													  bool useShadowBuffer)
	{
		GLES2HardwareIndexBuffer* buf =
		new GLES2HardwareIndexBuffer(this, itype, numIndexes, usage, true);
		{
			LOCK_MUTEX(mIndexBuffersMutex);
			mIndexBuffers.insert(buf);
		}
		return HardwareIndexBufferSharedPtr(buf);
	}
	
	RenderToVertexBufferSharedPtr GLES2HardwareBufferManagerBase::createRenderToVertexBuffer()
	{
		return RenderToVertexBufferSharedPtr();
	}
	
	GLenum GLES2HardwareBufferManagerBase::getGLUsage(unsigned int usage)
	{
		switch (usage)
		{
			case HardwareBuffer::HBU_STATIC:
			case HardwareBuffer::HBU_STATIC_WRITE_ONLY:
				return GL_STATIC_DRAW;
			case HardwareBuffer::HBU_DYNAMIC:
			case HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY:
				return GL_DYNAMIC_DRAW;
			case HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY_DISCARDABLE:
				return GL_STREAM_DRAW;
			default:
				return GL_DYNAMIC_DRAW;
		};
	}
	
	GLenum GLES2HardwareBufferManagerBase::getGLType(unsigned int type)
	{
		switch (type)
		{
			case VET_FLOAT1:
			case VET_FLOAT2:
			case VET_FLOAT3:
			case VET_FLOAT4:
				return GL_FLOAT;
			case VET_SHORT1:
			case VET_SHORT2:
			case VET_SHORT3:
			case VET_SHORT4:
				return GL_SHORT;
			case VET_COLOUR:
			case VET_COLOUR_ABGR:
			case VET_COLOUR_ARGB:
			case VET_UBYTE4:
				return GL_UNSIGNED_BYTE;
			default:
				return 0;
		};
	}
	
	void* GLES2HardwareBufferManagerBase::allocateScratch(uint32 size)
	{
		LOCK_MUTEX(mScratchMutex);
		if (size % 4 != 0)
		{
			size += 4 - (size % 4);
		}
		
		uint32 bufferPos = 0;
		while (bufferPos < SCRATCH_POOL_SIZE)
		{
			GLES2ScratchBufferAlloc* pNext = (GLES2ScratchBufferAlloc*)(mScratchBufferPool + bufferPos);
			if (pNext->free && pNext->size >= size)
			{
				if (pNext->size > size + sizeof(GLES2ScratchBufferAlloc))
				{
					uint32 offset = sizeof(GLES2ScratchBufferAlloc) + size;
					
					GLES2ScratchBufferAlloc* pSplitAlloc = (GLES2ScratchBufferAlloc*)
						(mScratchBufferPool + bufferPos + offset);
					pSplitAlloc->free = 1;
					pSplitAlloc->size = pNext->size - size - sizeof(GLES2ScratchBufferAlloc);
					pNext->size = size;
				}
				
				pNext->free = 0;
				return ++pNext;
			}
			bufferPos += sizeof(GLES2ScratchBufferAlloc) + pNext->size;
		}
		
		return 0;
	}
	
	void GLES2HardwareBufferManagerBase::deallocateScratch(void* ptr)
	{
		LOCK_MUTEX(mScratchMutex);
		
		uint32 bufferPos = 0;
		GLES2ScratchBufferAlloc* pLast = 0;
		while (bufferPos < SCRATCH_POOL_SIZE)
		{
			GLES2ScratchBufferAlloc* pCurrent = (GLES2ScratchBufferAlloc)(mScratchBufferPool + bufferPos);
			if ((mScratchBufferPool + bufferPos + sizeof(GLES2ScratchBufferAlloc)) == ptr)
			{
				pCurrent->free = 1;
				if (pLast && pLast->free)
				{
					bufferPos -= (pLast->size + sizeof(GLES2ScratchBufferAlloc));
					pLast->size += pCurrent->size + sizeof(GLES2ScratchBufferAlloc);
					pCurrent = pLast;
				}
				
				uint32 offset = bufferPos + pCurrent->size + sizeof(GLES2ScratchBufferAlloc);
				if (offset < SCRATCH_POOL_SIZE)
				{
					GLES2ScratchBufferAlloc* pNext = (GLES2ScratchBufferAlloc*)(
					mScratchBufferPool + offset);
					if (pNext->free)
					{
						pCurrent->size += pNext->size + sizeof(GLES2ScratchBufferAlloc);
					}
				}
				
				return;
			}
			
			bufferPos += sizeof(GLES2ScratchBufferAlloc) + pCurrent->size;
			pLast = pCurrent;
		}
		
		assert(false && "Memory deallocation error");
	}
	
	const size_t GLES2HardwareBufferManagerBase::getGLMapBufferThreshold() const
	{
		return mMapBufferThreshold;
	}
	
	void GLES2HardwareBufferManagerBase::setGLMapBufferThreshold(const size_t value)
	{
		mMapBufferThreshold = value;
	}
}
